28. 安全

如果添加了Spring Security的依赖,那么web应用默认对所有的HTTP路径(也称为终点,端点,表示API的具体网址)使用'basic'认证。为了给web应用添加方法级别(method-level)的保护,你可以添加@EnableGlobalMethodSecurity并使用想要的设置,其他信息参考Spring Security Reference

默认的AuthenticationManager只有一个用户('user'的用户名和随机密码会在应用启动时以INFO日志级别打印出来),如下:

Using default security password: 78fa095d-3f4c-48b1-ad50-e24c31d5cf35

如果你对日志配置进行微调,确保org.springframework.boot.autoconfigure.security类别记录日志级别为INFO,否则默认的密码不会打印出来。

你可以通过设置security.user.password改变默认密码,这些和其他有用的属性通过SecurityProperties(以"security"为前缀的属性)被外部化了。

默认的安全配置是通过SecurityAutoConfigurationSpringBootWebSecurityConfiguration(用于web安全),AuthenticationManagerConfiguration(可用于非web应用的认证配置)进行管理的。你可以添加一个@EnableWebSecurity bean来彻底关掉Spring Boot的默认配置。为了对它进行自定义,你需要使用外部的属性配置和WebSecurityConfigurerAdapter类型的beans(比如,添加基于表单的登陆)。 想要关闭认证管理的配置,你可以添加一个AuthenticationManager类型的bean,或在@Configuration类的某个方法里注入AuthenticationManagerBuilder来配置全局的AuthenticationManager。这里有一些安全相关的Spring Boot应用示例可以拿来参考。

在web应用中你能得到的开箱即用的基本特性如下:

  1. 一个使用内存存储的AuthenticationManager bean和一个用户(查看SecurityProperties.User获取user的属性)。
  2. 忽略(不保护)常见的静态资源路径(/css/**, /js/**, /images/**/webjars/****/favicon.ico)。
  3. 对其他所有路径实施HTTP Basic安全保护。
  4. 安全相关的事件会发布到Spring的ApplicationEventPublisher(成功和失败的认证,拒绝访问)。
  5. Spring Security提供的常见底层特性(HSTS, XSS, CSRF, 缓存)默认都被开启。

上述所有特性都能通过外部配置(security.*)打开,关闭,或修改。想要覆盖访问规则而不改变其他自动配置的特性,你可以添加一个注解@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)WebSecurityConfigurerAdapter类型的@Bean

WebSecurityConfigurerAdapter默认会匹配所有路径,如果不想完全覆盖Spring Boot自动配置的访问规则,你可以精确的配置想要覆盖的路径。

28.1 OAuth2

如果添加了spring-security-oauth2依赖,你可以利用自动配置简化认证(Authorization)或资源服务器(Resource Server)的设置,详情参考Spring Security OAuth 2 Developers Guide

28.1.1 授权服务器

想要创建一个授权服务器,并授予access tokens,你需要使用@EnableAuthorizationServer,并提供security.oauth2.client.client-idsecurity.oauth2.client.client-secret配置。

按以上操作后,你就能使用客户端证书创建一个access token,例如:

$ curl client:secret@localhost:8080/oauth/token -d grant_type=password -d username=user -d password=pwd

/token端点basic形式的认证证书是client-idclient-secret,用户证书通常是Spring Security的user详情(Spring Boot中默认是"user"和一个随机的密码)。

想要关闭自动配置,自己配置授权服务器特性,你只需添加一个AuthorizationServerConfigurer类型的@Bean

28.1.2 资源服务器

为了使用access token,你需要一个资源服务器(可以跟授权服务器是同一个)。创建资源服务器很简单,只需要添加@EnableResourceServer,提供一些配置以允许服务器解码access token。如果应用也是授权服务器,由于它知道如何去解码tokens,所以也就不需要做其他事情。如果你的app是独立的服务,那你就需要给它添加以下可选配置中的某一项:

  • security.oauth2.resource.user-info-uri用于/me资源(例如,PWS的https://uaa.run.pivotal.io/userinfo)。
  • security.oauth2.resource.token-info-uri用于token解码端点(例如,PWS的https://uaa.run.pivotal.io/check_token)。

如果user-info-uritoken-info-uri都指定了,你可以设置flag筛选出最想要的那个(默认prefer-token-info=true)。

另外,如果token是JWTs,你可以配置security.oauth2.resource.jwt.key-value解码它们(key是验签的key)。验签的键值可以是一个对称密钥,也可以是PEM编码的RSA公钥。如果你没有key,并且它是公开的,你可以通过security.oauth2.resource.jwt.key-uri提供一个下载URI(有一个"value"字段的JSON对象),例如,在PWS平台上:

$ curl https://uaa.run.pivotal.io/token_key
{"alg":"SHA256withRSA","value":"-----BEGIN PUBLIC KEY-----\nMIIBI...\n-----END PUBLIC KEY-----\n"}

如果你使用security.oauth2.resource.jwt.key-uri,授权服务器需要在应用启动时也运行起来,如果找不到key,它将输出warning,并告诉你如何解决。

28.2 User Info中的Token类型

Google和其他一些第三方身份(identity)提供商对发送给user info端点的请求头中设置的token类型名有严格要求。默认的Bearer满足大多数提供商要求,如果需要你可以设置security.oauth2.resource.token-type来改变它。

28.3 自定义User Info RestTemplate

如果设置了user-info-uri,资源服务器在内部将使用一个OAuth2RestTemplate抓取用于认证的用户信息,这是一个id为userInfoRestTemplate@Bean提供的,但你不需要了解这些,只需要用它即可。默认适用于大多数提供商,但偶尔你可能需要添加其他interceptors,或改变request的验证器(authenticator)。想要添加自定义,只需创建一个UserInfoRestTemplateCustomizer类型的bean —— 它只有单个方法,在bean创建后,初始化前会调用该方法。此处自定义的rest template仅用于内部执行认证。

在YAML中设置RSA key时,需要使用管道符分割多行(“|”),记得缩进key value,例如:

security:
    oauth2:
        resource:
            jwt:
                keyValue: |
                    -----BEGIN PUBLIC KEY-----
                    MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC...
                    -----END PUBLIC KEY-----

28.3.1 客户端

为了将web-app放入一个OAuth2客户端,你只需注解@EnableOAuth2Client,Spring Boot会创建OAuth2ClientContextOAuth2ProtectedResourceDetails,这些是创建OAuth2RestOperations必需的。Spring Boot不会自动创建该bean,但你自己创建也不费力:

@Bean
public OAuth2RestTemplate oauth2RestTemplate(OAuth2ClientContext oauth2ClientContext,
        OAuth2ProtectedResourceDetails details) {
    return new OAuth2RestTemplate(details, oauth2ClientContext);
}

你可能想添加一个限定名(qualifier),因为应用中可能定义多个RestTemplate

该配置使用security.oauth2.client.*作为证书(跟授权服务器使用的相同),此外,它也需要知道授权服务器中认证和token的URIs,例如:

security:
    oauth2:
        client:
            clientId: bd1c0a783ccdd1c9b9e4
            clientSecret: 1a9030fbca47a5b2c28e92f19050bb77824b5ad1
            accessTokenUri: https://github.com/login/oauth/access_token
            userAuthorizationUri: https://github.com/login/oauth/authorize
            clientAuthenticationScheme: form

具有该配置的应用在使用OAuth2RestTemplate时会重定向到GitHub以完成授权,如果已经登陆GitHub,你甚至不会注意到它已经授权过了。那些特殊的凭证(credentials)只在应用运行于8080端口时有效(为了更灵活,在GitHub或其他提供商上注册自己的客户端app)。

在客户端获取access token时,你可以设置security.oauth2.client.scope(逗号分隔或一个YAML数组)来限制它请求的作用域(scope)。作用域默认是空的,默认值取决于授权服务器,通常依赖于它拥有的客户端在注册时的设置。

security.oauth2.client.client-authentication-scheme也有设置,默认为"header"(如果你的OAuth2提供商不喜欢header认证,例如Github,你可能需要将它设置为“form”)。实际上,security.oauth2.client.*属性绑定到一个AuthorizationCodeResourceDetails实例,所以它的所有属性都可以指定。

在一个非web应用中,你仍旧可以创建一个OAuth2RestOperations,并且跟security.oauth2.client.*配置关联。在这种情况下,它是一个“client credentials token grant”,如果你使用它的话就需要获取(此处不需要注解@EnableOAuth2Client@EnableOAuth2Sso)。为了防止基础设施定义,只需要将security.oauth2.client.client-id从配置中移除(或将它设为空字符串)。

28.3.2 单点登陆

OAuth2客户端可用于从提供商抓取用户详情,然后转换为Spring Security需要的Authentication token。上述提到的资源服务器通过user-info-uri属性来支持该功能,这是基于OAuth2的单点登陆(SSO)协议最基本的,Spring Boot提供的@EnableOAuth2Sso注解让它更容易实践。通过添加该注解及端点配置(security.oauth2.client.*),Github客户端就可以使用/user/端点保护它的所有资源了:

security:
    oauth2:
...
    resource:
        userInfoUri: https://api.github.com/user
        preferTokenInfo: false

由于所有路径默认都处于保护下,也就没有主页展示那些未授权的用户,进而邀请他们去登陆(通过访问/login路径,或security.oauth2.sso.login-path指定的路径)。

为了自定义访问规则或保护的路径(这样你就可以添加主页),你可以将@EnableOAuth2Sso添加到一个WebSecurityConfigurerAdapter,该注解会包装它,增强需要的地方以使/login路径工作。例如,这里我们允许未授权的用户访问主页/,其他的依旧保持默认:

@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    public void init(WebSecurity web) {
        web.ignore("/");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**").authorizeRequests().anyRequest().authenticated();
    }

}

28.4 Actuator安全

如果Actuator处于使用中,你会发现:

  • 管理的端点是安全的,即使应用端点不安全。
  • Security事件转换为AuditEvents,并发布到AuditService
  • 默认用户有ADMINUSER角色。

Actuator的安全特性可以通过外部配置属性(management.security.*)进行修改。为了覆盖应用访问规则但不覆盖actuator的访问规则,你可以添加一个WebSecurityConfigurerAdapter类型的@Bean,并注解@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER),如果想覆盖actuator访问规则,则注解@Order(ManagementServerProperties.ACCESS_OVERRIDE_ORDER)